package def

import (
	"fmt"
	"strings"
	"strconv"
	"kumachan/standalone/rpc"
	"kumachan/standalone/rpc/kmd"
	"kumachan/interpreter/lang/common/source"
)


type FunctionEntity struct {
	Code  Code
	Info  FunctionInfo
}
type FunctionInfo struct {
	Symbol    Symbol  // should be empty if the entity is a lambda
	Location  source.Location
}

type FunctionSeed interface {
	Evaluate(ctx *FunctionSeedEvaluator) FuncValue
	GetInfo() *FunctionInfo
	fmt.Stringer
}
type FunctionSeedEvaluator struct {
	LibraryNativeFunctionCollection
	GeneratedNativeFunctionSeedEvaluator
	StaticValueSeedEvaluator
	DispatchTableMapping
}

type LibraryNativeFunctionCollection (func(id string) NativeFunction)

type DispatchTableMapping (map[DispatchTableIdentifier] *DispatchTable)
type DispatchTableDefMapping (map[DispatchTableIdentifier] DispatchTableDef)
type DispatchTableIdentifier struct {
	ConcreteTypeId   Symbol
	InterfaceTypeId  Symbol
}
type DispatchTableDef struct {
	Methods   [] MethodDef
	Included  [] DispatchTableIdentifier
}
type MethodDef struct {
	// either Function or Field
	Function  Symbol  // empty means using Field
	Field     ShortIndex
}
func (id DispatchTableIdentifier) String() string {
	return fmt.Sprintf("%s -> %s", id.ConcreteTypeId, id.InterfaceTypeId)
}
func (def DispatchTableDefMapping) Evaluate(get func(Symbol)(FuncValue)) DispatchTableMapping {
	var m = make(DispatchTableMapping)
	for id := range def {
		m[id] = new(DispatchTable)
	}
	for id, table_def := range def {
		var table = m[id]
		var methods = make([] FuncValue, len(table_def.Methods))
		for i, item := range table_def.Methods {
			if item.Function != (Symbol {}) {
				methods[i] = get(item.Function)
			} else {
				var index = item.Field
				methods[i] = ValFuncNative(func(arg Value, _ InteropContext) Value {
					return arg.(TupleValue).Elements[index]
				})
			}
		}
		var included = make([] *DispatchTable, len(table_def.Included))
		for i, item := range table_def.Included {
			var d, exists = m[item]
			if !(exists) { panic("something went wrong") }
			included[i] = d
		}
		table.Methods = methods
		table.Included = included
	}
	return m
}

type FunctionSeedLibraryNative struct {
	Id    string
	Info  FunctionInfo
}
func (s *FunctionSeedLibraryNative) GetInfo() *FunctionInfo {
	return &(s.Info)
}
func (s *FunctionSeedLibraryNative) String() string {
	return strconv.Quote(s.Id)
}
func (s *FunctionSeedLibraryNative) Evaluate(ctx *FunctionSeedEvaluator) FuncValue {
	return ValFuncNative(ctx.LibraryNativeFunctionCollection(s.Id))
}

type FunctionSeedGeneratedNative struct {
	Data  GeneratedNativeFunctionSeed
	Info  FunctionInfo
}
func (s *FunctionSeedGeneratedNative) GetInfo() *FunctionInfo {
	return &(s.Info)
}
func (s *FunctionSeedGeneratedNative) String() string {
	return s.Data.String()
}
func (s *FunctionSeedGeneratedNative) Evaluate(ctx *FunctionSeedEvaluator) FuncValue {
	var f = s.Data.Evaluate(ctx.GeneratedNativeFunctionSeedEvaluator)
	return ValFuncNative(f)
}

type FunctionSeedDataSerializer struct {
	Type  *kmd.Type
	Info  FunctionInfo
}
func (s FunctionSeedDataSerializer) GetInfo() *FunctionInfo {
	return &(s.Info)
}
func (s FunctionSeedDataSerializer) String() string {
	return fmt.Sprintf("serializer(%s)", s.Type)
}
func (s FunctionSeedDataSerializer) Evaluate(_ *FunctionSeedEvaluator) FuncValue {
	var t = s.Type
	return ValFuncNative(func(arg Value, h InteropContext) Value {
		var api = h.KmdApi()
		var binary, err = api.Serialize(arg, t)
		if err != nil {
			var wrapped = fmt.Errorf("serialization error: %w", err)
			panic(wrapped)
		}
		return binary
	})
}

type FunctionSeedDataDeserializer struct {
	Type  *kmd.Type
	Info  FunctionInfo
}
func (s FunctionSeedDataDeserializer) GetInfo() *FunctionInfo {
	return &(s.Info)
}
func (s FunctionSeedDataDeserializer) String() string {
	return fmt.Sprintf("deserializer(%s)", s.Type)
}
func (s FunctionSeedDataDeserializer) Evaluate(_ *FunctionSeedEvaluator) FuncValue {
	var t = s.Type
	return ValFuncNative(func(arg Value, h InteropContext) Value {
		var api = h.KmdApi()
		var obj, err = api.Deserialize(arg.([] byte), t)
		if err != nil { return Ng(err) }
		return Ok(obj)
	})
}

type FunctionSeedServiceIdentifier struct {
	Id    rpc.ServiceIdentifier
	Info  FunctionInfo
}
func (s FunctionSeedServiceIdentifier) GetInfo() *FunctionInfo {
	return &(s.Info)
}
func (s FunctionSeedServiceIdentifier) String() string {
	return fmt.Sprintf("service(%s)", s.Id)
}
func (s FunctionSeedServiceIdentifier) Evaluate(_ *FunctionSeedEvaluator) FuncValue {
	var value = s.Id
	return ValFuncNative(func(_ Value, _ InteropContext) Value {
		return value
	})
}

type FunctionSeedUsual struct {
	InstTree   [] Instruction
	Position   [] source.Position
	ExtBranch  [] ExternalBranchMapping
	ExtUpdate  [] ExternalUpdateIndexes
	Arguments  [] ShortIndex
	CtxSize    LocalSize
	Lambdas    [] *FunctionSeedUsual
	Static     [] StaticValueSeed
	GenStatic  GeneratedStaticDataSeed
	Info       FunctionInfo
}
func (seed *FunctionSeedUsual) GetInfo() *FunctionInfo {
	return &(seed.Info)
}
func (seed *FunctionSeedUsual) String() string {
	var buf strings.Builder
	// TODO: more detailed info
	if seed.Info.Symbol != (Symbol {}) {
		fmt.Fprintf(&buf, ".function %s\n", seed.Info.Symbol)
	}
	for i, inst := range seed.InstTree {
		fmt.Fprintf(&buf, "\t[%d] %s\n", i, inst)
	}
	for i, cl := range seed.Lambdas {
		fmt.Fprintf(&buf, ".lambda-%d\n", i)
		buf.WriteString(cl.String())
	}
	return buf.String()
}
func (seed *FunctionSeedUsual) Evaluate(ctx *FunctionSeedEvaluator) FuncValue {
	return ValFuncUsual(UsualFunction {
		Entity:  seed.CreateEntity(ctx),
		Context: nil,
	})
}
func (seed *FunctionSeedUsual) CreateEntity(ctx *FunctionSeedEvaluator) *FunctionEntity {
	var static = make([] Value, len(seed.Static))
	for i, item := range seed.Static {
		static[i] = item.Evaluate(ctx.StaticValueSeedEvaluator)
	}
	var gen_static = &GeneratedStaticData {
		DispatchTables: make([] *DispatchTable, len(seed.GenStatic.DispatchTables)),
		TransformPaths: make([] InterfaceTransformPath, len(seed.GenStatic.TransformPaths)),
	}
	for i, item := range seed.GenStatic.DispatchTables {
		gen_static.DispatchTables[i] = item.Evaluate(ctx.DispatchTableMapping)
	}
	for i, item := range seed.GenStatic.TransformPaths {
		gen_static.TransformPaths[i] = item
	}
	var closures = make([] *FunctionEntity, len(seed.Lambdas))
	for i, item := range seed.Lambdas {
		closures[i] = item.CreateEntity(ctx)
	}
	return &FunctionEntity {
		Code: Code {
			InstTree:  seed.InstTree,
			Position:  seed.Position,
			ExtBranch: seed.ExtBranch,
			ExtUpdate: seed.ExtUpdate,
			Arguments: seed.Arguments,
			CtxSize:   seed.CtxSize,
			Lambdas:   closures,
			Static:    static,
			GenStatic: gen_static,
		},
		Info: seed.Info,
	}
}


